package com.black.play.entity;

import com.black.play.component.SnakeWindow;
import com.black.play.constant.AppConstant;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @author ylx
 */
public class ClassicGridSnake extends Snake
{
    private final int k;
    // 匿名内部类加载配置
    private static final List<Food> FOODS = new ArrayList<Food>()
    {
        {
            // 初始化全屏网格
            for (int x = 0; x < WIDTH; x += NODE_SIZE.width)
            {
                for (int y = 0; y < HEIGHT; y += NODE_SIZE.height)
                {
                    this.add(new Food(x, y));
                }
            }
        }
    };

    /**
     * 实例化时构造必要参数
     */
    public ClassicGridSnake()
    {
        super();
        // 偏移量
        offsets = new int[][]{
                {0, -NODE_SIZE.height},
                {0, NODE_SIZE.height},
                {-NODE_SIZE.width, 0},
                {NODE_SIZE.width, 0}
        };
        this.k = (MAX_DELAY - MIN_DELAY) / (FOODS.size() - INIT_LENGTH);
    }

    /**
     * 随机初始化下一食物
     * 直接对列表深拷贝过于费时
     * 找到后对单独的进行拷贝
     *
     * @return 随机节点
     */
    protected Food randomFood()
    {
        List<Food> foods = new ArrayList<>(FOODS);
        foods.removeAll(body);
        Food randomFood = foods.get(RANDOM.nextInt(FOODS.size()));
        return randomFood.clone();
    }

    /**
     * 根据当前节点 随机方向初始化下一节点
     * 并递归调用直到蛇长满足初始化设置
     *
     * @param food       当前节点
     * @param directions 方向值
     */
    protected void init(Food food, Direction[] directions, int length)
    {
        // 长度满足初始化要求 退出递归
        if (length == this.length)
        {
            return;
        }
        // 随机方向
        Direction direction = directions[RANDOM.nextInt(directions.length)];
        List<Food> foods = new ArrayList<>(FOODS);
        foods.removeAll(body);
        // 根据方向获取下一个节点
        Food next = getNextByDirection(foods, food, direction);
        // 如果下一个节点不为空 设置颜色并添加到蛇身
        if (next != null)
        {
            next.setColor(Food.BODY_COLOR);
            body.add(next);
            // 根据新节点继续找下一节点
            init(next, directions, ++length);
        }
        else
        {
            // 当随机方向恰好是蛇身位置或者墙体位置
            // 则根据当前节点重新找下一节点
            init(food, directions, length);
        }
    }

    /**
     * 在给定集合中 根据当前节点和方向 看集合中是否有该方向上的邻居节点
     *
     * @param foods     节点列表  (传入除蛇身外全节点列表时可用于 生成平面连续的节点列表; 传入body时可以用于检测碰撞自身)
     * @param food      当前节点
     * @param direction 指定方向
     * @return 该节点该方向存在节点返回该节点, 否则返回null
     */
    public Food getNextByDirection(List<Food> foods, Food food, Direction direction)
    {
        // 获取方向在枚举中的下标 以获取偏移量
        int ordinal = direction.ordinal();
        int[] offset = offsets[ordinal];
        // 将当前节点偏移后的坐标 与 除去蛇身后的全节点列表进行过滤
        List<Food> collect = foods.stream().filter(item -> food.getX() + offset[0] == item.getX() && food.getY() + offset[1] == item.getY()).collect(Collectors.toList());
        return collect.isEmpty() ? null : collect.get(0);
    }

    @Override
    public Direction getDirectionByNext()
    {
        Food last = body.getLast();
        Food lastSecond = body.get(body.size() - 2);
        if (last.getX() == lastSecond.getX())
        {
            return last.getY() > lastSecond.getY() ? Direction.DOWN : Direction.UP;
        }
        return last.getX() > lastSecond.getX() ? Direction.RIGHT : Direction.LEFT;
    }

    @Override
    protected void addBody()
    {
        // 碰到后 身长自增
        length++;
        // 更新主窗口长度显示
        SnakeWindow instance = SnakeWindow.getInstance();
        instance.updateLength(length);
        // 更新主窗口分数
        int bodyOffset = length - INIT_LENGTH;
        instance.updateScore(bodyOffset << 1);
        // 更新主窗口速度显示
        int delay = MAX_DELAY - bodyOffset * k;
        instance.updateSpeed((double) AppConstant.SECOND / delay);
        // 更新定时器
        timer.setDelay(delay);
        // 获取旧头部节点并设置颜色
        Food head = body.getFirst();
        head.setColor(Food.BODY_COLOR);
        // 添加新头部
        food.setColor(Food.HEAD_COLOR);
        body.addFirst(food);
        // 随机生成新食物
        food = randomFood();
    }

    @Override
    protected void moveBody()
    {
        // 获取旧头部节点设置颜色
        Food head = body.getFirst();
        head.setColor(Food.BODY_COLOR);
        // 获取尾部作为新头部
        Food newHead = body.pollLast();
        assert newHead != null;
        // 更新为新头部坐标并设置颜色添加到队头
        newHead.setX(headX);
        newHead.setY(headY);
        newHead.setColor(Food.HEAD_COLOR);
        body.addFirst(newHead);
    }
}
